﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MMessaging.MessagingEx.Common;
using MMessaging.MessagingEx.Queues;
using TUtils.BasicHelper;

namespace MMessaging.MessagingEx.Services
{
	/// <summary>
	/// Basisklasse für MMessaging-Services, die eine Anbindung an einen
	/// Duplex-Messagechannel haben (bidirektionalen Nachrichtenkanal).
	/// Im Gegensatz zur Basisklasse "MServiceBase" bietet MServiceBaseDuplex
	/// die protected Methode "PostMessage(MESSAGETYPE message)" an.
	/// </summary>
	/// <typeparam name="MESSAGETYPE"></typeparam>
	public abstract class MServiceBaseDuplex<MESSAGETYPE> : MServiceBase<MESSAGETYPE>
		where MESSAGETYPE : class
	{
		#region private 

		/// <summary>
		/// Die Seite des Duplex-Channels, mit dem dieser Service verbunden ist.
		/// </summary>
		protected IMChannelEndDuplex<MESSAGETYPE> Channel { get; private set; }
		
		/// <summary>
		///  loopback device
		/// </summary>
		protected MChannelInterThreadPrio<MESSAGETYPE> LoopbackQueue { get; private set; } 

		/// <summary>
		/// Recieves messages from service-channel and loopback device
		/// </summary>
		private MChannelAdapterOut<MESSAGETYPE> _channelAdapter;

		/// <summary>
		///  loopback device
		/// </summary>
		//private MServiceHubBroadcast<MESSAGETYPE> _inputHub;

		private string _name;

		#endregion

		#region constructor

		public MServiceBaseDuplex()
		{
			LoopbackQueue = new MChannelInterThreadPrio<MESSAGETYPE>("");
		}

		/// <summary>
		/// Initialisierungsfunktion.
		/// </summary>
		/// <param name="messageHandlerName">
		/// Name des Service. Wird nur für Loggingzwecke genutzt.
		/// </param>
		/// <param name="channel">
		/// Die Seite des Duplex-Channels, mit dem dieser Service verbunden ist.
		/// </param>
		/// <param name="maxCountOfThreads">
		/// maximale Anzahl von Threads, die der Service erzeugen bzw. reservieren darf.
		/// Diese Anzahl ist abhängig von der maximalen Anzahl von fachlichen Abläufen,
		/// die theoretisch in der Serviceninstanz parallel ablaufen können. 
		/// </param>
		/// <param name="startAtOnce">
		/// true, wenn die Init() Methode den Service sofort starten soll.
		/// </param>
		/// <returns>this</returns>
		public MServiceBaseDuplex<MESSAGETYPE> Init(
			string messageHandlerName,
			IMChannelEndDuplex<MESSAGETYPE> channel,
			int maxCountOfThreads,
			bool startAtOnce)
		{
			Channel = channel;
			_name = messageHandlerName;

			LoopbackQueue.Name = messageHandlerName;
			_channelAdapter = new MChannelAdapterOut<MESSAGETYPE>(
				messageHandlerName,
				channel,
				LoopbackQueue);

			AddMessageHandler(OnMessageReceived);

			base.Init(
				messageHandlerName,
				_channelAdapter, 
				maxCountOfThreads,
				startAtOnce);

			return this;
		}

		private void OnMessageReceived(IMTask<MESSAGETYPE> task)
		{
			if (task.CurrentMessage is IDispatcherMessage)
			{
				var dispatcherMsg = task.CurrentMessage as IDispatcherMessage;
				dispatcherMsg.Method();
			}
		}

		/// <summary>
		/// Initialisierungsfunktion.
		/// Erzeugt außerdem automatisch einen Channel vom Typ MChannelInterThreadDuplex
		/// und verbindet den Service über diesen Channel mit dem gegebenen Node.
		/// </summary>
		/// <param name="node">
		/// Knoten, mit dem der Service verbunden werden soll
		/// </param>
		/// <param name="loggingNameOfService">
		/// Name des Service. Wird nur für Loggingzwecke genutzt.
		/// </param>
		/// <param name="maxCountOfThreads">
		/// maximale Anzahl von Threads, die der Service erzeugen bzw. reservieren darf.
		/// Diese Anzahl ist abhängig von der maximalen Anzahl von fachlichen Abläufen,
		/// die theoretisch in der Serviceninstanz parallel ablaufen können. 
		/// </param>
		/// <param name="startAtOnce">
		/// true, wenn die Init() Methode den Service sofort starten soll.
		/// </param>
		/// <returns></returns>
		public MServiceBaseDuplex<MESSAGETYPE> Init(
			string loggingNameOfService,
			IMNodeDuplex<MESSAGETYPE> node,
			int maxCountOfThreads,
			bool startAtOnce)
		{
			var channel = new MChannelInterThreadDuplex<MESSAGETYPE>(loggingNameOfService);

			node.AddDuplexChannelEnd(channel.ChannelEndLeft);

			return Init(
				loggingNameOfService,
				channel.ChannelEndRight,
				maxCountOfThreads,
				startAtOnce);
		}

		#endregion

		#region public

		/// <summary>
		/// Fügt die gegebene Message dem Ausgangspuffer des Duplex-Messagechannels hinzu.
		/// Außerdem (!) fügt sie die Message dem Eingangs-puffer des Duplex-Channels hinzu,
		/// was einem Loopback - Device gleichkommt. Das heißt also, dass der Service 
		/// seine gesendete Message auch immer selbst erhält.
		/// </summary>
		/// <param name="message"></param>
		public virtual void PostMessage(MESSAGETYPE message)
		{
			LoopbackQueue.PostMessage(message);
			Channel.PostMessage(message);
		}

		public void BeginInvoke<DISPATCHERMESSAGE>(Action action)
			where DISPATCHERMESSAGE : MESSAGETYPE, IDispatcherMessage, new()
		{
			var dispatchermsg = new DISPATCHERMESSAGE
			{
				Method = action
			};

			LoopbackQueue.PostMessage(dispatchermsg);
		}

		#endregion

		#region interface IMService<MESSAGETYPE>

		/// <summary>
		/// Stoppt den Service asynchron.
		/// </summary>
		public override void CancelAsync()
		{
			base.CancelAsync();
			//if (_inputHub != null)
			//    _inputHub.CancelAsync();
		}

		/// <summary>
		/// Muss aufgerufen werden, um den Service zu starten.
		/// Alternativ kann man auch in der Initialisierungsmethode den Parameter 
		/// "startAtOnce" setzen.
		/// StartAsync() darf nur aufgerufen werden, wenn der Service initialisiert worden
		/// ist (Aufruf von Init()).
		/// </summary>
		public override void StartAsync()
		{
			base.StartAsync();
			//if (_inputHub != null)
			//    _inputHub.StartAsync();
		}

		#endregion
	}
}
